package com.dimples.dd.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dimples.dd.common.constant.DDConstant;
import com.dimples.dd.common.util.collection.CollectionUtils;
import com.dimples.dd.mybatis.core.query.LambdaQueryWrapperX;
import com.dimples.dd.redis.core.RedisHelper;
import com.dimples.dd.redis.core.constant.RedisConstants;
import com.dimples.dd.system.core.dal.redis.RedisKeyConstants;
import com.dimples.dd.system.core.enums.MenuTypeEnum;
import com.dimples.dd.system.enums.permission.RoleCodeEnum;
import com.dimples.dd.system.mapper.SystemRoleMenuMapper;
import com.dimples.dd.system.mapper.SystemUserRoleMapper;
import com.dimples.dd.system.model.bo.RolePermsBO;
import com.dimples.dd.system.model.entity.SystemMenuDO;
import com.dimples.dd.system.model.entity.SystemRoleMenuDO;
import com.dimples.dd.system.model.entity.SystemUserRoleDO;
import com.dimples.dd.system.model.form.RoleHomeForm;
import com.dimples.dd.system.service.PermissionService;
import com.dimples.dd.system.service.SystemMenuService;
import com.dimples.dd.system.service.SystemRoleService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.dimples.dd.common.exception.BizExceptionUtil.exception;
import static com.dimples.dd.common.exception.ErrorCode.ROUTE_NAME_NOT_EXISTS;

/**
 * 权限 Service 实现类
 *
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2024/7/9
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PermissionServiceImpl implements PermissionService {

    private final RedisHelper redisHelper;

    @Autowired
    private SystemRoleService systemRoleService;

    @Autowired
    private SystemMenuService systemMenuService;

    @Autowired
    private SystemRoleMenuMapper systemRoleMenuMapper;

    @Autowired
    private SystemUserRoleMapper systemUserRoleMapper;

    /**
     * 初始化权限缓存
     */
    @PostConstruct
    public void initRolePermsCache() {
        refreshRolePermsCache();
    }

    @Override
    public void refreshRolePermsCache() {
        // 清理权限缓存
        redisHelper.strHDelete(RedisConstants.ROLE_PERMS_PREFIX, "*");

        log.info(DDConstant.CONFIG_LOG, "Cache Permission Info");

        List<RolePermsBO> list = this.systemRoleMenuMapper.selectRolePermsList(null);
        if (CollectionUtil.isNotEmpty(list)) {
            list.forEach(item -> {
                String roleCode = item.getRoleCode();
                Set<String> perms;
                // 如果是管理员的情况下，获取全部菜单编号
                if (RoleCodeEnum.isSuperAdmin(roleCode)) {
                    LambdaQueryWrapperX<SystemMenuDO> wrapperX = new LambdaQueryWrapperX<>();
                    perms = this.systemMenuService.list(wrapperX.eq(SystemMenuDO::getMenuType, MenuTypeEnum.BUTTON.getValue()))
                            .stream().map(SystemMenuDO::getPermission).collect(Collectors.toSet());
                } else {
                    perms = item.getPerms();
                }
                redisHelper.strHPut(RedisConstants.ROLE_PERMS_PREFIX, roleCode, JSONUtil.toJsonStr(perms.stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet())));
            });
        }
    }

    @Override
    public void refreshRolePermsCache(String roleCode) {
        // 清理权限缓存
        redisHelper.strHDelete(RedisConstants.ROLE_PERMS_PREFIX, roleCode);

        List<RolePermsBO> list = this.systemRoleMenuMapper.selectRolePermsList(roleCode);
        if (CollectionUtil.isNotEmpty(list)) {
            RolePermsBO rolePerms = list.get(0);
            if (rolePerms == null) {
                return;
            }

            Set<String> perms = rolePerms.getPerms();
            redisHelper.strHPut(RedisConstants.ROLE_PERMS_PREFIX, roleCode, JSONUtil.toJsonStr(perms));
        }
    }

    @Override
    public void refreshRolePermsCache(String oldRoleCode, String newRoleCode) {
        // 清理旧角色权限缓存
        redisHelper.strHDelete(RedisConstants.ROLE_PERMS_PREFIX, oldRoleCode);

        // 添加新角色权限缓存
        List<RolePermsBO> list = this.systemRoleMenuMapper.selectRolePermsList(newRoleCode);
        if (CollectionUtil.isNotEmpty(list)) {
            RolePermsBO rolePerms = list.get(0);
            if (rolePerms == null) {
                return;
            }

            Set<String> perms = rolePerms.getPerms();
            redisHelper.strHPut(RedisConstants.ROLE_PERMS_PREFIX, newRoleCode, JSONUtil.toJsonStr(perms));
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveUserRoles(Long userId, List<Long> roleIds) {
        if (userId == null || CollectionUtil.isEmpty(roleIds)) {
            return;
        }

        // 用户原角色ID集合
        List<Long> userRoleIds = this.systemUserRoleMapper.selectList(new LambdaQueryWrapper<SystemUserRoleDO>()
                        .eq(SystemUserRoleDO::getUserId, userId))
                .stream()
                .map(SystemUserRoleDO::getRoleId)
                .collect(Collectors.toList());

        // 新增用户角色
        List<Long> saveRoleIds;
        if (CollectionUtil.isEmpty(userRoleIds)) {
            saveRoleIds = roleIds;
        } else {
            saveRoleIds = roleIds.stream()
                    .filter(roleId -> !userRoleIds.contains(roleId))
                    .collect(Collectors.toList());
        }

        List<SystemUserRoleDO> saveUserRoles = saveRoleIds
                .stream()
                .map(roleId -> new SystemUserRoleDO(userId, roleId))
                .collect(Collectors.toList());
        this.systemUserRoleMapper.insertBatch(saveUserRoles);

        // 删除用户角色
        if (CollectionUtil.isNotEmpty(userRoleIds)) {
            List<Long> removeRoleIds = userRoleIds.stream()
                    .filter(roleId -> !roleIds.contains(roleId))
                    .collect(Collectors.toList());

            if (CollectionUtil.isNotEmpty(removeRoleIds)) {
                this.systemUserRoleMapper.delete(new LambdaQueryWrapper<SystemUserRoleDO>()
                        .eq(SystemUserRoleDO::getUserId, userId)
                        .in(SystemUserRoleDO::getRoleId, removeRoleIds)
                );
            }
        }
    }

    @Override
    public boolean hasAssignedUsers(Long roleId) {
        int count = this.systemUserRoleMapper.countUsersForRole(roleId);
        return count > 0;
    }

    @Override
    public List<Long> selectRoleIdsExistAssignedUsers(List<Long> roleIds) {
        return this.systemUserRoleMapper.selectRoleIdsExistAssignedUsers(roleIds);
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public void processMenuDeleted(Long menuId) {
        this.systemRoleMenuMapper.deleteListByMenuId(menuId);
    }

    @Override
    public List<Long> getRoleMenuIds(Long roleId) {
        return this.systemRoleMenuMapper.listMenuIdsByRoleId(roleId);
    }

    @Override
    public String getRoleHomePage(Long roleId) {
        SystemRoleMenuDO roleHomePageDO = this.systemRoleMenuMapper.selectRoleHomePageByRoleId(roleId);
        return ObjectUtil.isNotEmpty(roleHomePageDO) && StrUtil.isNotBlank(roleHomePageDO.getRoleDefaultHome()) ? roleHomePageDO.getRoleDefaultHome() : null;
    }

    @Override
    public void updateRoleHomePage(RoleHomeForm roleHomeForm) {
        SystemMenuDO systemMenuDO = this.systemMenuService.getOne(Wrappers.lambdaQuery(SystemMenuDO.class).eq(SystemMenuDO::getRouteName, roleHomeForm.getHomePage()));
        if (ObjectUtil.isEmpty(systemMenuDO)) {
            throw exception(ROUTE_NAME_NOT_EXISTS, roleHomeForm.getHomePage());
        }
        // 查询当前角色的首页
        SystemRoleMenuDO homePageByRoleId = this.systemRoleMenuMapper.selectRoleHomePageByRoleId(roleHomeForm.getRoleId());
        if (ObjectUtil.isNotEmpty(homePageByRoleId)) {
            homePageByRoleId.setRoleDefaultHome(null);
            this.systemRoleMenuMapper.updateById(homePageByRoleId);
        }
        SystemRoleMenuDO systemRoleMenuDO = this.systemRoleMenuMapper.selectOne(SystemRoleMenuDO::getRoleId, roleHomeForm.getRoleId(), SystemRoleMenuDO::getMenuId, systemMenuDO.getId());
        if (ObjectUtil.isNotEmpty(systemRoleMenuDO)) {
            if (!StrUtil.equals(systemRoleMenuDO.getRoleDefaultHome(), roleHomeForm.getHomePage())) {
                systemRoleMenuDO.setRoleDefaultHome(roleHomeForm.getHomePage());
                this.systemRoleMenuMapper.updateById(systemRoleMenuDO);
            }
        } else {
            SystemRoleMenuDO roleMenuDO = new SystemRoleMenuDO();
            roleMenuDO.setRoleId(roleHomeForm.getRoleId());
            roleMenuDO.setMenuId(systemMenuDO.getId());
            roleMenuDO.setRoleDefaultHome(roleHomeForm.getHomePage());
            this.systemRoleMenuMapper.insert(roleMenuDO);
        }
    }

    // ========== 角色-菜单的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
        // 获得角色拥有菜单编号
        List<SystemRoleMenuDO> systemRoleMenuList = this.systemRoleMenuMapper.selectListByRoleId(roleId);
        Set<Long> dbMenuIds = CollectionUtils.convertSet(systemRoleMenuList, SystemRoleMenuDO::getMenuId);
        // 计算新增和删除的菜单编号
        Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            this.systemRoleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                SystemRoleMenuDO entity = new SystemRoleMenuDO();
                entity.setRoleId(roleId);
                entity.setMenuId(menuId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            // 查询角色的首页菜单，不允许删除
            List<SystemRoleMenuDO> systemRoleMenus = this.systemRoleMenuMapper.selectList(SystemRoleMenuDO::getRoleId, roleId);
            if (CollUtil.isNotEmpty(systemRoleMenus)) {
                Set<Long> homePageMenuId = systemRoleMenus
                        .stream().filter(systemRoleMenuDO -> StrUtil.isNotBlank(systemRoleMenuDO.getRoleDefaultHome())).map(SystemRoleMenuDO::getMenuId).collect(Collectors.toSet());
                deleteMenuIds = CollUtil.subtract(deleteMenuIds, homePageMenuId);
            }

            this.systemRoleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }
        // 刷新角色的权限缓存
        this.refreshRolePermsCache(this.systemRoleService.getById(roleId).getCode());
    }

    @Override
    public Set<Long> getUserRoleIdListByUserId(Long userId) {
        return CollectionUtils.convertSet(this.systemUserRoleMapper.selectList(SystemUserRoleDO::getUserId, userId), SystemUserRoleDO::getRoleId);
    }

    @Override
    public Set<Long> getRoleMenuListByRoleId(Set<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (this.systemRoleService.hasAnySuperAdmin(roleIds)) {
            return CollectionUtils.convertSet(this.systemMenuService.list(), SystemMenuDO::getId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        return CollectionUtils.convertSet(this.systemRoleMenuMapper.selectListByRoleId(roleIds), SystemRoleMenuDO::getMenuId);
    }

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    public void assignUserRole(Long userId, Set<String> roleCodes) {
        // 获得用户拥有角色编号
        Set<Long> dbRoleIds = CollectionUtils.convertSet(this.systemUserRoleMapper.selectListByUserId(userId), SystemUserRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        List<Long> roleIdList = this.systemRoleService.listIdsByCodes(roleCodes.stream().toList());
        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            this.systemUserRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                SystemUserRoleDO entity = new SystemUserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            this.systemUserRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }
    }
}
























